package main

import (
	"fmt"
	"os"
	"sort"
	"sync"
	"time"

	"git.dev.tencent.com/lwch/runtime"
	"git.dev.tencent.com/lwch/torrent"
)

type downloadInfo struct {
	sync.RWMutex
	idx    int
	length int
	done   bool
	data   map[int]int // from => to
}

var downloads = make(map[int]*downloadInfo)

func (i *downloadInfo) pick(n int) int {
	type pair struct {
		from int
		to   int
	}
	var calc []pair
	i.RLock()
	for from, to := range i.data {
		calc = append(calc, pair{from, to})
	}
	i.RUnlock()
	sort.Slice(calc, func(i, j int) bool {
		return calc[i].from < calc[j].from
	})
	start := 0
	if len(calc) > 0 {
		start = calc[0].to
	}
	if start > n {
		return start
	}
	return n
}

func (i *downloadInfo) log(offset int) {
	type pair struct {
		from int
		to   int
	}
	var calc []pair
	i.RLock()
	for from, to := range i.data {
		calc = append(calc, pair{from, to})
	}
	i.RUnlock()
	for _, p := range calc {
		if p.to == offset {
			i.Lock()
			i.data[p.from] = p.to + blockSize
			i.Unlock()
			return
		}
	}
	i.Lock()
	i.data[offset] = offset + blockSize
	i.Unlock()

	i.merge()

	i.RLock()
	n := len(i.data)
	var to int
	for _, v := range i.data {
		to = v
		break
	}
	i.RUnlock()
	if n == 1 && to >= i.length {
		i.Lock()
		i.done = true
		i.Unlock()
		runtime.Log("download piece %d done", i.idx)
	}
}

func (i *downloadInfo) merge() {
	// merge data by the same end
	ends := make(map[int]int) // to => from
	i.RLock()
	for from, to := range i.data {
		if _, ok := ends[to]; !ok {
			ends[to] = from
		} else if from < ends[to] {
			ends[to] = from
		}
	}
	i.RUnlock()

	i.Lock()
	i.data = make(map[int]int)
	for to, from := range ends {
		i.data[from] = to
	}
	i.Unlock()
}

func download(t torrent.Torrent) {
	left := len(t.Info.Pieces)
	for i := 0; i < len(t.Info.Pieces); i++ {
		downloads[i] = &downloadInfo{
			idx:    i,
			length: t.Info.PieceLength,
			data:   make(map[int]int),
		}
	}
	for left > 0 {
		left = 0
		cnt := 0
		for i, info := range downloads {
			info.RLock()
			done := info.done
			info.RUnlock()
			if done {
				continue
			}
			left++
			start := 0
			for _, c := range clients.choose(i) {
				start = info.pick(start)
				if start >= t.Info.PieceLength {
					break
				}
				if blocking.isBlocking(i, start) {
					continue
				}
				blocking.begin(c, i, start)
				c.download(i, start)
				cnt++
			}
		}
		if cnt == 0 {
			time.Sleep(time.Second)
		}
	}
}

func save(idx, offset uint32, data []byte) {
	runtime.Assert(os.MkdirAll("files", 0755))
	f, err := os.OpenFile(fmt.Sprintf("files/%d", idx), os.O_CREATE|os.O_RDWR, 0644)
	runtime.Assert(err)
	defer f.Close()
	_, err = f.WriteAt(data, int64(offset))
	runtime.Assert(err)
	downloads[int(idx)].log(int(offset))
}
